{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "8d5f6463",
   "metadata": {
    "colab_type": "text",
    "id": "view-in-github"
   },
   "source": [
    "<a href=\"https://colab.research.google.com/github/tomasonjo/blogs/blob/master/nomad/NomadList.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6fe207d7",
   "metadata": {
    "id": "6fe207d7"
   },
   "source": [
    "A couple of days ago, I stumbled upon the network visualizations of travel logs made by Pieter Levels on his NomadList website. NomadList is a website that builds the infrastructure to help digital nomads live anywhere in the world and connect with like-minded people. So I asked Pieter on Twitter if he could share the underlying data for travel logs, and lo and behold, the very next day, he made the aggregated travel logs data publicly available. The only fitting next step for me was to take that data and plug it into Neo4j and play around with it. This blog post will present some of my findings and also show you how to use the new Link Prediction Pipeline to predict new connections in a graph.\n",
    "\n",
    "If you want to follow along with examples in this post, I recommend using a Blank project in Neo4j Sandbox. It is a free cloud instance of Neo4j database that comes pre-installed with both APOC and Graph Data Science plugins."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "PUEiXiQNWQvq",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "PUEiXiQNWQvq",
    "outputId": "9eaa20c5-7283-429f-85de-271abb31ec38"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Collecting neo4j\n",
      "  Downloading neo4j-4.4.2.tar.gz (89 kB)\n",
      "\u001b[?25l\r",
      "\u001b[K     |███▋                            | 10 kB 19.8 MB/s eta 0:00:01\r",
      "\u001b[K     |███████▎                        | 20 kB 23.3 MB/s eta 0:00:01\r",
      "\u001b[K     |███████████                     | 30 kB 29.0 MB/s eta 0:00:01\r",
      "\u001b[K     |██████████████▋                 | 40 kB 23.7 MB/s eta 0:00:01\r",
      "\u001b[K     |██████████████████▎             | 51 kB 18.0 MB/s eta 0:00:01\r",
      "\u001b[K     |██████████████████████          | 61 kB 20.7 MB/s eta 0:00:01\r",
      "\u001b[K     |█████████████████████████▋      | 71 kB 21.4 MB/s eta 0:00:01\r",
      "\u001b[K     |█████████████████████████████▎  | 81 kB 23.1 MB/s eta 0:00:01\r",
      "\u001b[K     |████████████████████████████████| 89 kB 7.0 MB/s \n",
      "\u001b[?25hRequirement already satisfied: pytz in /usr/local/lib/python3.7/dist-packages (from neo4j) (2018.9)\n",
      "Building wheels for collected packages: neo4j\n",
      "  Building wheel for neo4j (setup.py) ... \u001b[?25l\u001b[?25hdone\n",
      "  Created wheel for neo4j: filename=neo4j-4.4.2-py3-none-any.whl size=115365 sha256=f78776c174e2fec97922454b202c049ba89dadba2ef00129f76610384fed5946\n",
      "  Stored in directory: /root/.cache/pip/wheels/10/d6/28/95029d7f69690dbc3b93e4933197357987de34fbd44b50a0e4\n",
      "Successfully built neo4j\n",
      "Installing collected packages: neo4j\n",
      "Successfully installed neo4j-4.4.2\n"
     ]
    }
   ],
   "source": [
    "!pip install neo4j"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "a076c480",
   "metadata": {
    "id": "a076c480"
   },
   "outputs": [],
   "source": [
    "# Define Neo4j connections\n",
    "import pandas as pd\n",
    "from neo4j import GraphDatabase\n",
    "host = 'bolt://3.231.25.240:7687'\n",
    "user = 'neo4j'\n",
    "password = 'hatchets-visitor-axes'\n",
    "driver = GraphDatabase.driver(host,auth=(user, password))\n",
    "\n",
    "def run_query(query):\n",
    "    with driver.session() as session:\n",
    "        result = session.run(query)\n",
    "        return pd.DataFrame([r.values() for r in result], columns=result.keys())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4f3d1ae0",
   "metadata": {
    "id": "4f3d1ae0"
   },
   "source": [
    "The graph model consists of cities, which are represented as nodes, and travel logs described as relationships.The travel logs are already aggregated in the underlying data, so we only know how many travels were made from one city to another. Unfortunately, there is no time component in the data, so we can't compare pre and post-pandemic travels or how long a person stayed in that city. All we know is that all travels happened somewhere between 2014 and 2021, and the data is available for top 50 cities per continent. On a more technical level, we are dealing with a directed weighted graph.\n",
    "# Graph import\n",
    "If you want to follow along, open your Neo4j Sandbox instance and copy the Cypher queries as we go along. I have also prepared all the code in a form of Jupyter Notebook if you want to use that.\n",
    "First, we will define a unique property constraint on City nodes. The unique property constraint ensures that property values are unique for all nodes of a specific label, but also automatically adds an index to that property for faster imports and queries."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "c0407155",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 49
    },
    "id": "c0407155",
    "outputId": "5d762e43-26be-4edd-fae1-178f4962a2f6"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "Empty DataFrame\n",
       "Columns: []\n",
       "Index: []"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "run_query(\"\"\"\n",
    "CREATE CONSTRAINT IF NOT EXISTS FOR (c:City) REQUIRE c.id IS UNIQUE;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9bff7060",
   "metadata": {
    "id": "9bff7060"
   },
   "source": [
    "Next, we will import the travel logs from the NomadList website. They are conveniently available in JSON format on the website. We can use the apoc.load.json procedure to retrieve the data from the page. We will also use apoc.periodic.iterate procedure for batching purposes. Read more about how to batch transactions in the documentation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "09261727",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 142
    },
    "id": "09261727",
    "outputId": "1cf0c058-6391-4cc4-c80d-a955fd5c1dce"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>batches</th>\n",
       "      <th>total</th>\n",
       "      <th>timeTaken</th>\n",
       "      <th>committedOperations</th>\n",
       "      <th>failedOperations</th>\n",
       "      <th>failedBatches</th>\n",
       "      <th>retries</th>\n",
       "      <th>errorMessages</th>\n",
       "      <th>batch</th>\n",
       "      <th>operations</th>\n",
       "      <th>wasTerminated</th>\n",
       "      <th>failedParams</th>\n",
       "      <th>updateStatistics</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>34</td>\n",
       "      <td>332</td>\n",
       "      <td>15</td>\n",
       "      <td>332</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>{}</td>\n",
       "      <td>{'total': 34, 'committed': 34, 'failed': 0, 'e...</td>\n",
       "      <td>{'total': 332, 'committed': 332, 'failed': 0, ...</td>\n",
       "      <td>False</td>\n",
       "      <td>{}</td>\n",
       "      <td>{'nodesDeleted': 0, 'labelsAdded': 332, 'relat...</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   batches  total  timeTaken  committedOperations  failedOperations  \\\n",
       "0       34    332         15                  332                 0   \n",
       "\n",
       "   failedBatches  retries errorMessages  \\\n",
       "0              0        0            {}   \n",
       "\n",
       "                                               batch  \\\n",
       "0  {'total': 34, 'committed': 34, 'failed': 0, 'e...   \n",
       "\n",
       "                                          operations  wasTerminated  \\\n",
       "0  {'total': 332, 'committed': 332, 'failed': 0, ...          False   \n",
       "\n",
       "  failedParams                                   updateStatistics  \n",
       "0           {}  {'nodesDeleted': 0, 'labelsAdded': 332, 'relat...  "
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "run_query(\"\"\"\n",
    "CALL apoc.periodic.iterate('\n",
    "  CALL apoc.load.json(\"https://nomadlist.com/graph.json\")\n",
    "  YIELD value\n",
    "  WITH value, [x in keys(value) WHERE x <> \"README\" | x] AS keys\n",
    "  UNWIND keys AS source_city\n",
    "  WITH source_city, value\n",
    "  RETURN source_city, value\n",
    "','\n",
    "  MERGE (s:City{name:source_city})\n",
    "  WITH value[source_city] as destinations, s\n",
    "  WHERE destinations <> []\n",
    "  WITH destinations, keys(destinations) as destination_cities, s\n",
    "  UNWIND destination_cities AS destination_city\n",
    "  MERGE (t:City{name:destination_city})\n",
    "  MERGE (s)-[r:TRAVEL_TO]->(t)\n",
    "  SET r.weight = destinations[destination_city]', \n",
    "  {batchSize:10})\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "51245ca1",
   "metadata": {
    "id": "51245ca1"
   },
   "source": [
    "The distance between cities is not available in the original dataset. You could use APOC spatial procedures to retrieve GPS locations. To avoid spamming the OSM server, I have prepared a CSV file with all the city locations and store it on GitHub. Just like fetching JSON files from the internet, you can also retrieve CSV files from any webpage. Run the following query to import locations of cities."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "dfa60524",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 49
    },
    "id": "dfa60524",
    "outputId": "75b0d59f-6e33-47e1-dcb5-eb66537c522b"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "Empty DataFrame\n",
       "Columns: []\n",
       "Index: []"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "run_query(\"\"\"\n",
    "LOAD CSV WITH HEADERS FROM \"https://raw.githubusercontent.com/tomasonjo/blog-datasets/main/nomad/nomad_cities_location.csv\" as row\n",
    "MATCH (c:City)\n",
    "WHERE c.name = row.city\n",
    "SET c.location = point({latitude:toFloat(row.latitude), longitude:toFloat(row.longitude)})\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8f91e56f",
   "metadata": {
    "id": "8f91e56f"
   },
   "source": [
    "Before we jump into analysis, the last thing is to calculate the distance between cities based on their location information."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "fc6aa2b3",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 49
    },
    "id": "fc6aa2b3",
    "outputId": "5179ab96-d6c9-493c-c167-04f2f465230e"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "Empty DataFrame\n",
       "Columns: []\n",
       "Index: []"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "run_query(\"\"\"\n",
    "MATCH (s:City)-[r:TRAVEL_TO]->(t:City)\n",
    "WITH r,point.distance(s.location, t.location) / 1000 AS distanceInKm\n",
    "SET r.distance = distanceInKm\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dd7694a5",
   "metadata": {
    "id": "dd7694a5"
   },
   "source": [
    "# Exploratory graph analysis\n",
    "\n",
    "Let's first examine how many travel logs are represented in our graph. Remember, the data was already aggregated before we got it, so we need to look at the total sum of the weight property for all travel relationships."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "123b41dc",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 81
    },
    "id": "123b41dc",
    "outputId": "a332eb8e-aa96-40e3-d531-8f4730abc028"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>all_travels</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>30329</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   all_travels\n",
       "0        30329"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "run_query(\"\"\"\n",
    "MATCH p=()-[r:TRAVEL_TO]->()\n",
    "RETURN sum(r.weight) as all_travels\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "808cb97e",
   "metadata": {
    "id": "808cb97e"
   },
   "source": [
    "In my case, I got a total of 32639 travel logs. You are likely to get a higher number as I believe the data is not static and is updated regularly. Now it is time to examine which are the most popular destinations. For those who like technical terms, we will investigate nodes with the highest weighted in-degree."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "cb2f562e",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 363
    },
    "id": "cb2f562e",
    "outputId": "4e273a12-b04e-474f-b571-5ed40c6843da"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>city</th>\n",
       "      <th>travels</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>bangkok-thailand</td>\n",
       "      <td>2152</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>london-united-kingdom</td>\n",
       "      <td>1698</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>chiang-mai-thailand</td>\n",
       "      <td>1446</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>lisbon-portugal</td>\n",
       "      <td>1193</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>new-york-city-ny-united-states</td>\n",
       "      <td>1187</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>berlin-germany</td>\n",
       "      <td>1095</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>mexico-city-mexico</td>\n",
       "      <td>946</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td>canggu-bali-indonesia</td>\n",
       "      <td>916</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8</th>\n",
       "      <td>kuala-lumpur-malaysia</td>\n",
       "      <td>847</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9</th>\n",
       "      <td>budapest-hungary</td>\n",
       "      <td>727</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                             city  travels\n",
       "0                bangkok-thailand     2152\n",
       "1           london-united-kingdom     1698\n",
       "2             chiang-mai-thailand     1446\n",
       "3                 lisbon-portugal     1193\n",
       "4  new-york-city-ny-united-states     1187\n",
       "5                  berlin-germany     1095\n",
       "6              mexico-city-mexico      946\n",
       "7           canggu-bali-indonesia      916\n",
       "8           kuala-lumpur-malaysia      847\n",
       "9                budapest-hungary      727"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "run_query(\"\"\"\n",
    "MATCH (c:City)<-[r:TRAVEL_TO]-()\n",
    "RETURN c.name as city, sum(r.weight) as travels\n",
    "ORDER BY travels DESC\n",
    "LIMIT 10\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fb3e43d8",
   "metadata": {
    "id": "fb3e43d8"
   },
   "source": [
    "Surprisingly, London is in second place. I would never imagine that, however Bangkok place makes a lot of sense from my perspective. It would be interesting to know if people are only passing through London to get to their final destination or are they actually staying there longer. We lack that information, so we can only hypothesize. All in all, seems that Thailand and Western Europe are popular destinations for digital nomads. For good measure, also Singapore, New York and San Francisco are included.\n",
    "I am also interested in how far do people like to travel on average. We added the distance property to relationships so that we can examine their distribution roughly. I've prepared a Cypher query that creates four bins and calculates the ratios of travels within that distance divided by all the travels."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "0c7c8e4e",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 81
    },
    "id": "0c7c8e4e",
    "outputId": "ed81e932-56dc-45ea-e158-0989e6134e7b"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>within_500</th>\n",
       "      <th>within_1000</th>\n",
       "      <th>within_2000</th>\n",
       "      <th>rest</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>0.268522</td>\n",
       "      <td>0.175937</td>\n",
       "      <td>0.155099</td>\n",
       "      <td>0.29025</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   within_500  within_1000  within_2000     rest\n",
       "0    0.268522     0.175937     0.155099  0.29025"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "run_query(\"\"\"\n",
    "MATCH ()-[r:TRAVEL_TO]->()\n",
    "RETURN sum(CASE WHEN r.distance < 500 THEN r.weight END) \n",
    "          / toFloat(sum(r.weight)) AS within_500,\n",
    "sum(CASE WHEN 500 < r.distance < 1000 THEN r.weight END) \n",
    "          / toFloat(sum(r.weight)) AS within_1000,\n",
    "sum(CASE WHEN 1000 < r.distance < 2000 THEN r.weight END) \n",
    "          / toFloat(sum(r.weight)) AS within_2000,\n",
    "sum(CASE WHEN 2000 < r.distance  THEN r.weight END) \n",
    "          / toFloat(sum(r.weight)) AS rest\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0b0617c4",
   "metadata": {
    "id": "0b0617c4"
   },
   "source": [
    "Almost 50% of all logged travels are less than 1000km. As a European, I imagine that trips up to 1000km are one night-train away, and as far I have heard for Americans, that's almost like a commute to work. Even a 2000km trip is, on average, only a two-hour flight. For the rest 30% of travels, I imagine they are most likely cross-continental trips. My hypothesis is that digital nomads like to travel across continents, and then explore that continent by moving from city to city. Of course, this is only my hypothesis, and I could be wrong.\n",
    "# Graph Data Science\n",
    "Now we will take advantage of graph algorithms available in the Neo4j Graph Data Science library for our further analysis. We will begin by projecting in-memory graph that will be used as basis for executing graph algorithms. If you want to learn more about the Graph Data Science library and how it works, I would suggest the free Introduction to Graph Algorithms course."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "46e5d48f",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 81
    },
    "id": "46e5d48f",
    "outputId": "0efac5f9-a3ef-4969-a512-310831f7a6f7"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>nodeProjection</th>\n",
       "      <th>relationshipProjection</th>\n",
       "      <th>graphName</th>\n",
       "      <th>nodeCount</th>\n",
       "      <th>relationshipCount</th>\n",
       "      <th>projectMillis</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>{'City': {'label': 'City', 'properties': {}}}</td>\n",
       "      <td>{'TRAVEL_TO': {'orientation': 'NATURAL', 'inde...</td>\n",
       "      <td>nomad</td>\n",
       "      <td>332</td>\n",
       "      <td>2422</td>\n",
       "      <td>301</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                  nodeProjection  \\\n",
       "0  {'City': {'label': 'City', 'properties': {}}}   \n",
       "\n",
       "                              relationshipProjection graphName  nodeCount  \\\n",
       "0  {'TRAVEL_TO': {'orientation': 'NATURAL', 'inde...     nomad        332   \n",
       "\n",
       "   relationshipCount  projectMillis  \n",
       "0               2422            301  "
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "run_query(\"\"\"\n",
    "CALL gds.graph.project('nomad', 'City', 'TRAVEL_TO', {relationshipProperties:'weight'});\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d7ec9625",
   "metadata": {
    "id": "d7ec9625"
   },
   "source": [
    "Almost any graph analysis starts with Weakly-Connected component algorithm. It is used to find disconnected parts or islands of nodes within the graph. To fetch the high-level stats of the WCC results, you can execute the following query:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "c48859b3",
   "metadata": {
    "id": "c48859b3",
    "outputId": "6d7a7c6d-929c-4a95-d061-961efe5c3e8e"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>componentCount</th>\n",
       "      <th>componentDistribution</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>31</td>\n",
       "      <td>{'p99': 302, 'min': 1, 'max': 302, 'mean': 10....</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   componentCount                              componentDistribution\n",
       "0              31  {'p99': 302, 'min': 1, 'max': 302, 'mean': 10...."
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "run_query(\"\"\"\n",
    "CALL gds.wcc.stats('nomad')\n",
    "YIELD componentCount, componentDistribution;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d39d474c",
   "metadata": {
    "id": "d39d474c"
   },
   "source": [
    "The WCC algorithm found 38 disconnected components, with the largest having 301 members. Judging by the percentile values, it seems that there is a single super component with 301 members, and then there are 31 components with only a single member. To inspect the cities that don't have any connection with the rest of the world, we can use the following Cypher query:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "8fa23746",
   "metadata": {
    "id": "8fa23746",
    "outputId": "3622a779-fb9c-4001-865d-329ff1359850"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>city</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>constantine-algeria</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>boumerdas-algeria</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>lubumbashi-dr-congo</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>davenport-ia-united-states</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>kokomo-in-united-states</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>zaria-nigeria</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>harlingen-tx-united-states</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td>intag-valley-ecuador</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8</th>\n",
       "      <td>ploiesti-romania</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9</th>\n",
       "      <td>port-said-egypt</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                         city\n",
       "0         constantine-algeria\n",
       "1           boumerdas-algeria\n",
       "2         lubumbashi-dr-congo\n",
       "3  davenport-ia-united-states\n",
       "4     kokomo-in-united-states\n",
       "5               zaria-nigeria\n",
       "6  harlingen-tx-united-states\n",
       "7        intag-valley-ecuador\n",
       "8            ploiesti-romania\n",
       "9             port-said-egypt"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "run_query(\"\"\"\n",
    "MATCH (c:City)\n",
    "WHERE NOT exists { (c)--() }\n",
    "RETURN c.name as city\n",
    "LIMIT 10;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0fcdca63",
   "metadata": {
    "id": "0fcdca63"
   },
   "source": [
    "While these cities are mentioned in the NomadList dataset, they don't have any connection with the outer world.\n",
    "# Betweenness centrality\n",
    "Next, we will take a look at the Betweenness centrality. The Betweenness centrality is used to identify bridges or connections between clusters of nodes. It is calculated by finding the shortest paths between all pairs of nodes and then looking at the count of when a node appears on those shortest paths. You can execute the betweenness centrality on the nomadlist projected in-memory graph as follows:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "aa490855",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 363
    },
    "id": "aa490855",
    "outputId": "c887f819-f57c-41f1-d9b1-4a07fd86ac31"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>city</th>\n",
       "      <th>score</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>london-united-kingdom</td>\n",
       "      <td>12137.179384</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>dubai-united-arab-emirates</td>\n",
       "      <td>7959.631693</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>new-york-city-ny-united-states</td>\n",
       "      <td>6571.295256</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>cape-town-south-africa</td>\n",
       "      <td>6142.518404</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>lisbon-portugal</td>\n",
       "      <td>4577.633995</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>bangkok-thailand</td>\n",
       "      <td>4447.884818</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>mexico-city-mexico</td>\n",
       "      <td>4204.977539</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td>cairo-egypt</td>\n",
       "      <td>3924.075527</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8</th>\n",
       "      <td>buenos-aires-argentina</td>\n",
       "      <td>3829.651509</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9</th>\n",
       "      <td>los-angeles-ca-united-states</td>\n",
       "      <td>3782.754309</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                             city         score\n",
       "0           london-united-kingdom  12137.179384\n",
       "1      dubai-united-arab-emirates   7959.631693\n",
       "2  new-york-city-ny-united-states   6571.295256\n",
       "3          cape-town-south-africa   6142.518404\n",
       "4                 lisbon-portugal   4577.633995\n",
       "5                bangkok-thailand   4447.884818\n",
       "6              mexico-city-mexico   4204.977539\n",
       "7                     cairo-egypt   3924.075527\n",
       "8          buenos-aires-argentina   3829.651509\n",
       "9    los-angeles-ca-united-states   3782.754309"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "run_query(\"\"\"\n",
    "CALL gds.betweenness.stream('nomad')\n",
    "YIELD nodeId, score\n",
    "RETURN gds.util.asNode(nodeId).name AS city, score\n",
    "ORDER BY score DESC LIMIT 10\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "289c449a",
   "metadata": {
    "id": "289c449a"
   },
   "source": [
    "One important disclaimer is that, in this example, the betweenness centrality doesn't take the relationship weights into account. If you were to take a trip around the world and follow the typical digital nomad travel patterns, you would likely end up at these cities at some point in time. For example, if you travel between Europe and Asia, you have good chances of ending up in Dubai sooner or later. I already mentioned before that I would be interested in learning if nomads stay longer in London or visit it only to fly out across continents. Similarly, you could interpret these results as having New York and Mexico be entry or exit points to North America.\n",
    "# Network visualizations\n",
    "Now, we will create a lovely feature image for this blog post. I am only partially kidding, but with smaller datasets like this one, it makes sense to develop nice network visualizations as an excellent visualization can be worth more than a thousand words.\n",
    "We will use the PageRank score to determine the size of the nodes. In the context of PageRank, each relationship can be interpreted as a vote of confidence. In our example with the NomadList travel logs, the most popular nomad destinations should have the highest score. This is because the PageRank algorithm considers both the number of incoming connections as well as where those connections are coming from."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "7aaa5d41",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 81
    },
    "id": "7aaa5d41",
    "outputId": "d8514e18-0e0b-4c2c-c72c-9a29ccb302cc"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>writeMillis</th>\n",
       "      <th>nodePropertiesWritten</th>\n",
       "      <th>ranIterations</th>\n",
       "      <th>didConverge</th>\n",
       "      <th>centralityDistribution</th>\n",
       "      <th>postProcessingMillis</th>\n",
       "      <th>preProcessingMillis</th>\n",
       "      <th>computeMillis</th>\n",
       "      <th>configuration</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>13</td>\n",
       "      <td>332</td>\n",
       "      <td>20</td>\n",
       "      <td>False</td>\n",
       "      <td>{'p99': 10.982054710388184, 'min': 0.149999618...</td>\n",
       "      <td>119</td>\n",
       "      <td>1</td>\n",
       "      <td>576</td>\n",
       "      <td>{'maxIterations': 20, 'writeConcurrency': 4, '...</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   writeMillis  nodePropertiesWritten  ranIterations  didConverge  \\\n",
       "0           13                    332             20        False   \n",
       "\n",
       "                              centralityDistribution  postProcessingMillis  \\\n",
       "0  {'p99': 10.982054710388184, 'min': 0.149999618...                   119   \n",
       "\n",
       "   preProcessingMillis  computeMillis  \\\n",
       "0                    1            576   \n",
       "\n",
       "                                       configuration  \n",
       "0  {'maxIterations': 20, 'writeConcurrency': 4, '...  "
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "run_query(\"\"\"\n",
    "CALL gds.pageRank.write('nomad', {relationshipWeightProperty:'weight', writeProperty:'pagerank'});\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "467dc03b",
   "metadata": {
    "id": "467dc03b"
   },
   "source": [
    "To determine the color of the nodes in our visualization, we will use the Louvain Modularity algorithm. The Louvain Modularity algorithm is a community detection or clustering algorithm that groups together nodes that are densely connected."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "67c15e60",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 142
    },
    "id": "67c15e60",
    "outputId": "919ae63e-8d8f-4648-f7f0-4fa430d5f022"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>writeMillis</th>\n",
       "      <th>nodePropertiesWritten</th>\n",
       "      <th>modularity</th>\n",
       "      <th>modularities</th>\n",
       "      <th>ranLevels</th>\n",
       "      <th>communityCount</th>\n",
       "      <th>communityDistribution</th>\n",
       "      <th>postProcessingMillis</th>\n",
       "      <th>preProcessingMillis</th>\n",
       "      <th>computeMillis</th>\n",
       "      <th>configuration</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>117</td>\n",
       "      <td>332</td>\n",
       "      <td>0.64614</td>\n",
       "      <td>[0.596348599203333, 0.6461403734966755]</td>\n",
       "      <td>2</td>\n",
       "      <td>39</td>\n",
       "      <td>{'p99': 69, 'min': 1, 'max': 69, 'mean': 8.512...</td>\n",
       "      <td>2</td>\n",
       "      <td>0</td>\n",
       "      <td>2499</td>\n",
       "      <td>{'maxIterations': 10, 'writeConcurrency': 4, '...</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   writeMillis  nodePropertiesWritten  modularity  \\\n",
       "0          117                    332     0.64614   \n",
       "\n",
       "                              modularities  ranLevels  communityCount  \\\n",
       "0  [0.596348599203333, 0.6461403734966755]          2              39   \n",
       "\n",
       "                               communityDistribution  postProcessingMillis  \\\n",
       "0  {'p99': 69, 'min': 1, 'max': 69, 'mean': 8.512...                     2   \n",
       "\n",
       "   preProcessingMillis  computeMillis  \\\n",
       "0                    0           2499   \n",
       "\n",
       "                                       configuration  \n",
       "0  {'maxIterations': 10, 'writeConcurrency': 4, '...  "
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "run_query(\"\"\"\n",
    "CALL gds.louvain.write('nomad', {relationshipWeightProperty:'weight', writeProperty:'louvain'});\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6eef9f11",
   "metadata": {
    "id": "6eef9f11"
   },
   "source": [
    "If you are using Neo4j Sandbox or Desktop, you can open the Neo4j Bloom and recreate the following visualization. Check the documentation if you want to learn more about rule-based network visualization in Neo4j Bloom."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "11b68487",
   "metadata": {
    "id": "11b68487"
   },
   "source": [
    "# Link prediction pipeline\n",
    "Link Prediction Pipeline was added in the 1.7 version of the Neo4j Graph Data Science library. Link prediction is a technique that does exactly what you would imagine. It is used to predict new connections in the network that do not exists yet. You can think of it as a binary classification model that indicates if a new link is likely to exist between pairs of nodes. There are several applications for link prediction ranging from recommender systems to drug repurposing. To start, you first have to define a new link prediction pipeline."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "6bbfda37",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 81
    },
    "id": "6bbfda37",
    "outputId": "7367c747-1670-428c-e557-bd2beaaf0638"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>name</th>\n",
       "      <th>nodePropertySteps</th>\n",
       "      <th>featureSteps</th>\n",
       "      <th>splitConfig</th>\n",
       "      <th>autoTuningConfig</th>\n",
       "      <th>parameterSpace</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>lp-pipeline</td>\n",
       "      <td>[]</td>\n",
       "      <td>[]</td>\n",
       "      <td>{'negativeSamplingRatio': 1.0, 'testFraction':...</td>\n",
       "      <td>{'maxTrials': 10}</td>\n",
       "      <td>{'MultilayerPerceptron': [], 'RandomForest': [...</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "          name nodePropertySteps featureSteps  \\\n",
       "0  lp-pipeline                []           []   \n",
       "\n",
       "                                         splitConfig   autoTuningConfig  \\\n",
       "0  {'negativeSamplingRatio': 1.0, 'testFraction':...  {'maxTrials': 10}   \n",
       "\n",
       "                                      parameterSpace  \n",
       "0  {'MultilayerPerceptron': [], 'RandomForest': [...  "
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "run_query(\"\"\"\n",
    "CALL gds.beta.pipeline.linkPrediction.create('lp-pipeline');\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1cf5b092",
   "metadata": {
    "id": "1cf5b092"
   },
   "source": [
    "As a part of the link prediction pipeline, you can add intermediate steps where other graph algorithms are executed. You want to run graph algorithms which output will be used as link prediction classifier features. In this example, we will be using the FastRP node embeddings as input features for the link prediction model. You can read more about the FastRP algorithm in this excellent article by my friend Clair Sullivan. \n",
    "https://towardsdatascience.com/behind-the-scenes-on-the-fast-random-projection-algorithm-for-generating-graph-embeddings-efb1db0895"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "ebaf7d12",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 81
    },
    "id": "ebaf7d12",
    "outputId": "995a5697-153b-43ad-8b4a-0da476e96fed"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>name</th>\n",
       "      <th>nodePropertySteps</th>\n",
       "      <th>featureSteps</th>\n",
       "      <th>splitConfig</th>\n",
       "      <th>autoTuningConfig</th>\n",
       "      <th>parameterSpace</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>lp-pipeline</td>\n",
       "      <td>[{'name': 'gds.fastRP.mutate', 'config': {'ran...</td>\n",
       "      <td>[]</td>\n",
       "      <td>{'negativeSamplingRatio': 1.0, 'testFraction':...</td>\n",
       "      <td>{'maxTrials': 10}</td>\n",
       "      <td>{'MultilayerPerceptron': [], 'RandomForest': [...</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "          name                                  nodePropertySteps  \\\n",
       "0  lp-pipeline  [{'name': 'gds.fastRP.mutate', 'config': {'ran...   \n",
       "\n",
       "  featureSteps                                        splitConfig  \\\n",
       "0           []  {'negativeSamplingRatio': 1.0, 'testFraction':...   \n",
       "\n",
       "    autoTuningConfig                                     parameterSpace  \n",
       "0  {'maxTrials': 10}  {'MultilayerPerceptron': [], 'RandomForest': [...  "
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "run_query(\"\"\"\n",
    "CALL gds.beta.pipeline.linkPrediction.addNodeProperty(\n",
    "  'lp-pipeline', \n",
    "  'fastRP', {\n",
    "    mutateProperty: 'embedding',\n",
    "    featureProperties:['pagerank'],\n",
    "    embeddingDimension: 128,\n",
    "    randomSeed: 42\n",
    "});\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9af0d27a",
   "metadata": {
    "id": "9af0d27a"
   },
   "source": [
    "Here, we have defined that the link prediction pipeline should start by executing the FastRP algorithm, which will output an embedding for all the nodes in the network. You can also fine-tune algorithm hyper-parameters in the configuration of this step. Next, we need to define how to combine a link's source and target node features to produce an input to the link prediction model. Remember, the link prediction model in Neo4j GDS is a binary classification model that uses logistic regression under the hood. The output is either a 1 or 0 if a connection exists in the network or not, and the input features are combined by considering both source and target node features. At the moment, the pipeline features three different ways of combining node features:\n",
    "* L2 \n",
    "* Hadamard\n",
    "* Cosine\n",
    "\n",
    "You can inspect the documentation for more information about combining node features as an input to the link prediction model. In this example, we will use the L2 combiner to combine the FastRP node embeddings of pairs of nodes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "136abf2b",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 81
    },
    "id": "136abf2b",
    "outputId": "48c0bdf4-8b84-4a65-fa62-952b10a7a0c9"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>featureSteps</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>[{'name': 'COSINE', 'config': {'nodeProperties...</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                        featureSteps\n",
       "0  [{'name': 'COSINE', 'config': {'nodeProperties..."
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "run_query(\"\"\"\n",
    "CALL gds.beta.pipeline.linkPrediction.addFeature('lp-pipeline', 'cosine', {\n",
    "  nodeProperties: ['embedding']\n",
    "}) YIELD featureSteps;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b04219b7",
   "metadata": {
    "id": "b04219b7"
   },
   "source": [
    "Now it is time to define the train-test split. We can define the test-train split ratio. The pipeline uses k-fold crossvalidation training technique. A nice thing about the train-test split is that we don't have to develop a manual data split."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "2f50acad",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 81
    },
    "id": "2f50acad",
    "outputId": "e081e60c-3477-4ce0-82f3-cf20a4463e7d"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>splitConfig</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>{'negativeSamplingRatio': 1.0, 'testFraction':...</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                         splitConfig\n",
       "0  {'negativeSamplingRatio': 1.0, 'testFraction':..."
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "run_query(\"\"\"\n",
    "CALL gds.beta.pipeline.linkPrediction.configureSplit(\n",
    " 'lp-pipeline', {  \n",
    "   testFraction: 0.3,\n",
    "   trainFraction: 0.6,\n",
    "   validationFolds: 7})\n",
    "YIELD splitConfig;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a8b3db0c",
   "metadata": {
    "id": "a8b3db0c"
   },
   "source": [
    "The last configuration we have to set before we can train the model are link prediction model hyperparameters. You can provide many variations of model hyperparameters. In this example, we will provide three different combinations of hyperparameters and the model will automatically pick the combination that provides the best results during training."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "7f8de8f0",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 81
    },
    "id": "7f8de8f0",
    "outputId": "ca436f7d-0efa-4ee4-f06a-a5876220d29d"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>parameterSpace</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>{'MultilayerPerceptron': [], 'RandomForest': [...</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                      parameterSpace\n",
       "0  {'MultilayerPerceptron': [], 'RandomForest': [..."
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "run_query(\"\"\"\n",
    "CALL gds.beta.pipeline.linkPrediction.addLogisticRegression(\n",
    "  'lp-pipeline',  \n",
    "    {tolerance: 0.001, maxEpochs: 500})\n",
    "YIELD parameterSpace;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "59083762",
   "metadata": {
    "id": "59083762"
   },
   "source": [
    "Again, we need to project the in-memory graph before we start. The subtle difference from before is that here we are projecting the relationships as undirected. At the moment, the link prediction pipeline supports predicting only undirected relationships."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "b10cca46",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 81
    },
    "id": "b10cca46",
    "outputId": "b0fb1479-8325-4d8d-9b63-e7fec93e3253"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>nodeProjection</th>\n",
       "      <th>relationshipProjection</th>\n",
       "      <th>graphName</th>\n",
       "      <th>nodeCount</th>\n",
       "      <th>relationshipCount</th>\n",
       "      <th>projectMillis</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>{'City': {'label': 'City', 'properties': {'pag...</td>\n",
       "      <td>{'TRAVEL_TO': {'orientation': 'UNDIRECTED', 'i...</td>\n",
       "      <td>lp-graph</td>\n",
       "      <td>332</td>\n",
       "      <td>4844</td>\n",
       "      <td>61</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                      nodeProjection  \\\n",
       "0  {'City': {'label': 'City', 'properties': {'pag...   \n",
       "\n",
       "                              relationshipProjection graphName  nodeCount  \\\n",
       "0  {'TRAVEL_TO': {'orientation': 'UNDIRECTED', 'i...  lp-graph        332   \n",
       "\n",
       "   relationshipCount  projectMillis  \n",
       "0               4844             61  "
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "run_query(\"\"\"\n",
    "CALL gds.graph.project('lp-graph', \n",
    "  'City', \n",
    "  {TRAVEL_TO:{orientation:'UNDIRECTED'}}, \n",
    "  {nodeProperties:['pagerank']});\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "258de88b",
   "metadata": {
    "id": "258de88b"
   },
   "source": [
    "And finally, we get to train our link prediction model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "2821866c",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 81
    },
    "id": "2821866c",
    "outputId": "166d74f0-14a5-49a6-d9f1-ce5c5755a55d"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>winningModel</th>\n",
       "      <th>trainGraphScore</th>\n",
       "      <th>testGraphScore</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>{'maxEpochs': 500, 'minEpochs': 1, 'classWeigh...</td>\n",
       "      <td>0.795744</td>\n",
       "      <td>0.831011</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                        winningModel  trainGraphScore  \\\n",
       "0  {'maxEpochs': 500, 'minEpochs': 1, 'classWeigh...         0.795744   \n",
       "\n",
       "   testGraphScore  \n",
       "0        0.831011  "
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "run_query(\"\"\"\n",
    "CALL gds.beta.pipeline.linkPrediction.train('lp-graph', \n",
    "  {pipeline: 'lp-pipeline',\n",
    "   modelName: 'lp-model',\n",
    "   randomSeed: 42,\n",
    "   targetRelationshipType:\"TRAVEL_TO\"})\n",
    "YIELD modelInfo\n",
    "RETURN  modelInfo.bestParameters AS winningModel,  modelInfo.metrics.AUCPR.outerTrain AS trainGraphScore,  modelInfo.metrics.AUCPR.test AS testGraphScore;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "947e5c5b",
   "metadata": {
    "id": "947e5c5b"
   },
   "source": [
    "You can use the mutate mode to produce classification results and store the predicted relationships back to the projected graph."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "5257a912",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 81
    },
    "id": "5257a912",
    "outputId": "c6bb6343-9976-41ab-cbbf-9a00bcb89778"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>relationshipsWritten</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>40</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   relationshipsWritten\n",
       "0                    40"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "run_query(\"\"\"\n",
    "CALL gds.beta.pipeline.linkPrediction.predict.mutate('lp-graph', \n",
    "  {modelName: 'lp-model',  \n",
    "   mutateRelationshipType: 'TRAVEL_PREDICTED',\n",
    "   topN: 20,\n",
    "   threshold: 0.45})\n",
    "YIELD relationshipsWritten;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d2461e71",
   "metadata": {
    "id": "d2461e71"
   },
   "source": [
    "As the final step, you can stream and inspect results with the following Cypher query."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "89eac1e8",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 363
    },
    "id": "89eac1e8",
    "outputId": "c0766510-f369-4f4b-f390-fa85e7b2bf50"
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>city1</th>\n",
       "      <th>city2</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>fayetteville-nc-united-states</td>\n",
       "      <td>columbus-ga-united-states</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>benin-city-nigeria</td>\n",
       "      <td>port-harcourt-nigeria</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>ljubljana-slovenia</td>\n",
       "      <td>dubrovnik-croatia</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>krabi-thailand</td>\n",
       "      <td>penang-malaysia</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>dresden-germany</td>\n",
       "      <td>krakow-poland</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>panama-city-panama</td>\n",
       "      <td>minca-colombia</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>merida-mexico</td>\n",
       "      <td>la-paz-mexico</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td>perth-australia</td>\n",
       "      <td>byron-bay-australia</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8</th>\n",
       "      <td>dresden-germany</td>\n",
       "      <td>gdansk-poland</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9</th>\n",
       "      <td>galapagos-islands-ecuador</td>\n",
       "      <td>cuenca-ecuador</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                           city1                      city2\n",
       "0  fayetteville-nc-united-states  columbus-ga-united-states\n",
       "1             benin-city-nigeria      port-harcourt-nigeria\n",
       "2             ljubljana-slovenia          dubrovnik-croatia\n",
       "3                 krabi-thailand            penang-malaysia\n",
       "4                dresden-germany              krakow-poland\n",
       "5             panama-city-panama             minca-colombia\n",
       "6                  merida-mexico              la-paz-mexico\n",
       "7                perth-australia        byron-bay-australia\n",
       "8                dresden-germany              gdansk-poland\n",
       "9      galapagos-islands-ecuador             cuenca-ecuador"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "run_query(\"\"\"\n",
    "CALL gds.graph.streamRelationshipProperty('lp-graph', \n",
    "  'probability', \n",
    "  ['TRAVEL_PREDICTED'])\n",
    "YIELD  sourceNodeId, targetNodeId, propertyValue\n",
    "WHERE sourceNodeId < targetNodeId\n",
    "WITH  gds.util.asNode(sourceNodeId).name as city1, gds.util.asNode(targetNodeId).name as city2, propertyValue AS probability\n",
    "ORDER BY probability DESC\n",
    "RETURN city1, city2\n",
    "LIMIT 10;\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d1acd047",
   "metadata": {
    "id": "d1acd047"
   },
   "source": [
    "Our link prediction mostly predicts new local connections. For example, it predicts new travels between Madeira and Ericeira in Portual or Tangier and Agadir in Morocco. Similarly, it predicts new connections between neighhbouring countries like Cambodia, Vietnam, and Thailand. The only prediction that seems a bit off is connecting Conakry in Guinea to Abbotsford in Canada."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5abdf868",
   "metadata": {
    "id": "5abdf868"
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "colab": {
   "include_colab_link": true,
   "name": "NomadList.ipynb",
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
